home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / Other Langs / python / python folder / Lib / aifc.py next >
Encoding:
Text File  |  1994-01-05  |  28.6 KB  |  1,003 lines  |  [TEXT/????]

  1. # Stuff to parse AIFF-C and AIFF files.
  2. #
  3. # Unless explicitly stated otherwise, the description below is true
  4. # both for AIFF-C files and AIFF files.
  5. #
  6. # An AIFF-C file has the following structure.
  7. #
  8. #    +-----------------+
  9. #    | FORM            |
  10. #    +-----------------+
  11. #    | <size>          |
  12. #    +----+------------+
  13. #    |    | AIFC       |
  14. #    |    +------------+
  15. #    |    | <chunks>   |
  16. #    |    |    .       |
  17. #    |    |    .       |
  18. #    |    |    .       |
  19. #    +----+------------+
  20. #
  21. # An AIFF file has the string "AIFF" instead of "AIFC".
  22. #
  23. # A chunk consists of an identifier (4 bytes) followed by a size (4 bytes,
  24. # big endian order), followed by the data.  The size field does not include
  25. # the size of the 8 byte header.
  26. #
  27. # The following chunk types are recognized.
  28. #
  29. #    FVER
  30. #        <version number of AIFF-C defining document> (AIFF-C only).
  31. #    MARK
  32. #        <# of markers> (2 bytes)
  33. #        list of markers:
  34. #            <marker ID> (2 bytes, must be > 0)
  35. #            <position> (4 bytes)
  36. #            <marker name> ("pstring")
  37. #    COMM
  38. #        <# of channels> (2 bytes)
  39. #        <# of sound frames> (4 bytes)
  40. #        <size of the samples> (2 bytes)
  41. #        <sampling frequency> (10 bytes, IEEE 80-bit extended
  42. #            floating point)
  43. #        in AIFF-C files only:
  44. #        <compression type> (4 bytes)
  45. #        <human-readable version of compression type> ("pstring")
  46. #    SSND
  47. #        <offset> (4 bytes, not used by this program)
  48. #        <blocksize> (4 bytes, not used by this program)
  49. #        <sound data>
  50. #
  51. # A pstring consists of 1 byte length, a string of characters, and 0 or 1
  52. # byte pad to make the total length even.
  53. #
  54. # Usage.
  55. #
  56. # Reading AIFF files:
  57. #    f = aifc.open(file, 'r')
  58. # where file is either the name of a file or an open file pointer.
  59. # The open file pointer must have methods read(), seek(), and close().
  60. # In some types of audio files, if the setpos() method is not used,
  61. # the seek() method is not necessary.
  62. #
  63. # This returns an instance of a class with the following public methods:
  64. #    getnchannels()    -- returns number of audio channels (1 for
  65. #               mono, 2 for stereo)
  66. #    getsampwidth()    -- returns sample width in bytes
  67. #    getframerate()    -- returns sampling frequency
  68. #    getnframes()    -- returns number of audio frames
  69. #    getcomptype()    -- returns compression type ('NONE' for AIFF files)
  70. #    getcompname()    -- returns human-readable version of
  71. #               compression type ('not compressed' for AIFF files)
  72. #    getparams()    -- returns a tuple consisting of all of the
  73. #               above in the above order
  74. #    getmarkers()    -- get the list of marks in the audio file or None
  75. #               if there are no marks
  76. #    getmark(id)    -- get mark with the specified id (raises an error
  77. #               if the mark does not exist)
  78. #    readframes(n)    -- returns at most n frames of audio
  79. #    rewind()    -- rewind to the beginning of the audio stream
  80. #    setpos(pos)    -- seek to the specified position
  81. #    tell()        -- return the current position
  82. #    close()        -- close the instance (make it unusable)
  83. # The position returned by tell(), the position given to setpos() and
  84. # the position of marks are all compatible and have nothing to do with
  85. # the actual postion in the file.
  86. # The close() method is called automatically when the class instance
  87. # is destroyed.
  88. #
  89. # Writing AIFF files:
  90. #    f = aifc.open(file, 'w')
  91. # where file is either the name of a file or an open file pointer.
  92. # The open file pointer must have methods write(), tell(), seek(), and
  93. # close().
  94. #
  95. # This returns an instance of a class with the following public methods:
  96. #    aiff()        -- create an AIFF file (AIFF-C default)
  97. #    aifc()        -- create an AIFF-C file
  98. #    setnchannels(n)    -- set the number of channels
  99. #    setsampwidth(n)    -- set the sample width
  100. #    setframerate(n)    -- set the frame rate
  101. #    setnframes(n)    -- set the number of frames
  102. #    setcomptype(type, name)
  103. #            -- set the compression type and the
  104. #               human-readable compression type
  105. #    setparams(nchannels, sampwidth, framerate, nframes, comptype, compname)
  106. #            -- set all parameters at once
  107. #    setmark(id, pos, name)
  108. #            -- add specified mark to the list of marks
  109. #    tell()        -- return current position in output file (useful
  110. #               in combination with setmark())
  111. #    writeframesraw(data)
  112. #            -- write audio frames without pathing up the
  113. #               file header
  114. #    writeframes(data)
  115. #            -- write audio frames and patch up the file header
  116. #    close()        -- patch up the file header and close the
  117. #               output file
  118. # You should set the parameters before the first writeframesraw or
  119. # writeframes.  The total number of frames does not need to be set,
  120. # but when it is set to the correct value, the header does not have to
  121. # be patched up.
  122. # It is best to first set all parameters, perhaps possibly the
  123. # compression type, and then write audio frames using writeframesraw.
  124. # When all frames have been written, either call writeframes('') or
  125. # close() to patch up the sizes in the header.
  126. # Marks can be added anytime.  If there are any marks, ypu must call
  127. # close() after all frames have been written.
  128. # The close() method is called automatically when the class instance
  129. # is destroyed.
  130. #
  131. # When a file is opened with the extension '.aiff', an AIFF file is
  132. # written, otherwise an AIFF-C file is written.  This default can be
  133. # changed by calling aiff() or aifc() before the first writeframes or
  134. # writeframesraw.
  135.  
  136. import builtin
  137. import AL
  138. try:
  139.     import CL
  140. except ImportError:
  141.     pass
  142.  
  143. Error = 'aifc.Error'
  144.  
  145. _AIFC_version = 0xA2805140        # Version 1 of AIFF-C
  146.  
  147. _skiplist = 'COMT', 'INST', 'MIDI', 'AESD', \
  148.       'APPL', 'NAME', 'AUTH', '(c) ', 'ANNO'
  149.  
  150. _nchannelslist = [(1, AL.MONO), (2, AL.STEREO)]
  151. _sampwidthlist = [(8, AL.SAMPLE_8), (16, AL.SAMPLE_16), (24, AL.SAMPLE_24)]
  152. _frameratelist = [(48000, AL.RATE_48000),
  153.           (44100, AL.RATE_44100),
  154.           (32000, AL.RATE_32000),
  155.           (22050, AL.RATE_22050),
  156.           (16000, AL.RATE_16000),
  157.           (11025, AL.RATE_11025),
  158.           ( 8000,  AL.RATE_8000)]
  159.  
  160. def _convert1(value, list):
  161.     for t in list:
  162.         if value == t[0]:
  163.             return t[1]
  164.     raise Error, 'unknown parameter value'
  165.  
  166. def _convert2(value, list):
  167.     for t in list:
  168.         if value == t[1]:
  169.             return t[0]
  170.     raise Error, 'unknown parameter value'
  171.  
  172. def _read_long(file):
  173.     x = 0L
  174.     for i in range(4):
  175.         byte = file.read(1)
  176.         if byte == '':
  177.             raise EOFError
  178.         x = x*256 + ord(byte)
  179.     if x >= 0x80000000L:
  180.         x = x - 0x100000000L
  181.     return int(x)
  182.  
  183. def _read_ulong(file):
  184.     x = 0L
  185.     for i in range(4):
  186.         byte = file.read(1)
  187.         if byte == '':
  188.             raise EOFError
  189.         x = x*256 + ord(byte)
  190.     return x
  191.  
  192. def _read_short(file):
  193.     x = 0
  194.     for i in range(2):
  195.         byte = file.read(1)
  196.         if byte == '':
  197.             raise EOFError
  198.         x = x*256 + ord(byte)
  199.     if x >= 0x8000:
  200.         x = x - 0x10000
  201.     return x
  202.  
  203. def _read_string(file):
  204.     length = ord(file.read(1))
  205.     data = file.read(length)
  206.     if length & 1 == 0:
  207.         dummy = file.read(1)
  208.     return data
  209.  
  210. _HUGE_VAL = 1.79769313486231e+308 # See <limits.h>
  211.  
  212. def _read_float(f): # 10 bytes
  213.     import math
  214.     expon = _read_short(f) # 2 bytes
  215.     sign = 1
  216.     if expon < 0:
  217.         sign = -1
  218.         expon = expon + 0x8000
  219.     himant = _read_ulong(f) # 4 bytes
  220.     lomant = _read_ulong(f) # 4 bytes
  221.     if expon == himant == lomant == 0:
  222.         f = 0.0
  223.     elif expon == 0x7FFF:
  224.         f = _HUGE_VAL
  225.     else:
  226.         expon = expon - 16383
  227.         f = (himant * 0x100000000L + lomant) * pow(2.0, expon - 63)
  228.     return sign * f
  229.  
  230. def _write_short(f, x):
  231.     d, m = divmod(x, 256)
  232.     f.write(chr(d))
  233.     f.write(chr(m))
  234.  
  235. def _write_long(f, x):
  236.     if x < 0:
  237.         x = x + 0x100000000L
  238.     data = []
  239.     for i in range(4):
  240.         d, m = divmod(x, 256)
  241.         data.insert(0, m)
  242.         x = d
  243.     for i in range(4):
  244.         f.write(chr(int(data[i])))
  245.  
  246. def _write_string(f, s):
  247.     f.write(chr(len(s)))
  248.     f.write(s)
  249.     if len(s) & 1 == 0:
  250.         f.write(chr(0))
  251.  
  252. def _write_float(f, x):
  253.     import math
  254.     if x < 0:
  255.         sign = 0x8000
  256.         x = x * -1
  257.     else:
  258.         sign = 0
  259.     if x == 0:
  260.         expon = 0
  261.         himant = 0
  262.         lomant = 0
  263.     else:
  264.         fmant, expon = math.frexp(x)
  265.         if expon > 16384 or fmant >= 1:        # Infinity or NaN
  266.             expon = sign|0x7FFF
  267.             himant = 0
  268.             lomant = 0
  269.         else:                    # Finite
  270.             expon = expon + 16382
  271.             if expon < 0:            # denormalized
  272.                 fmant = math.ldexp(fmant, expon)
  273.                 expon = 0
  274.             expon = expon | sign
  275.             fmant = math.ldexp(fmant, 32)
  276.             fsmant = math.floor(fmant)
  277.             himant = long(fsmant)
  278.             fmant = math.ldexp(fmant - fsmant, 32)
  279.             fsmant = math.floor(fmant)
  280.             lomant = long(fsmant)
  281.     _write_short(f, expon)
  282.     _write_long(f, himant)
  283.     _write_long(f, lomant)
  284.  
  285. class Chunk:
  286.     def __init__(self, file):
  287.         self.file = file
  288.         self.chunkname = self.file.read(4)
  289.         if len(self.chunkname) < 4:
  290.             raise EOFError
  291.         self.chunksize = _read_long(self.file)
  292.         self.size_read = 0
  293.         self.offset = self.file.tell()
  294.  
  295.     def rewind(self):
  296.         self.file.seek(self.offset, 0)
  297.         self.size_read = 0
  298.  
  299.     def setpos(self, pos):
  300.         if pos < 0 or pos > self.chunksize:
  301.             raise RuntimeError
  302.         self.file.seek(self.offset + pos, 0)
  303.         self.size_read = pos
  304.         
  305.     def read(self, length):
  306.         if self.size_read >= self.chunksize:
  307.             return ''
  308.         if length > self.chunksize - self.size_read:
  309.              length = self.chunksize - self.size_read
  310.         data = self.file.read(length)
  311.         self.size_read = self.size_read + len(data)
  312.         return data
  313.  
  314.     def skip(self):
  315.         try:
  316.             self.file.seek(self.chunksize - self.size_read, 1)
  317.         except RuntimeError:
  318.             while self.size_read < self.chunksize:
  319.                 dummy = self.read(8192)
  320.                 if not dummy:
  321.                     raise EOFError
  322.         if self.chunksize & 1:
  323.             dummy = self.read(1)
  324.  
  325. class Aifc_read:
  326.     # Variables used in this class:
  327.     #
  328.     # These variables are available to the user though appropriate
  329.     # methods of this class:
  330.     # _file -- the open file with methods read(), close(), and seek()
  331.     #        set through the __init__() method
  332.     # _nchannels -- the number of audio channels
  333.     #        available through the getnchannels() method
  334.     # _nframes -- the number of audio frames
  335.     #        available through the getnframes() method
  336.     # _sampwidth -- the number of bytes per audio sample
  337.     #        available through the getsampwidth() method
  338.     # _framerate -- the sampling frequency
  339.     #        available through the getframerate() method
  340.     # _comptype -- the AIFF-C compression type ('NONE' if AIFF)
  341.     #        available through the getcomptype() method
  342.     # _compname -- the human-readable AIFF-C compression type
  343.     #        available through the getcomptype() method
  344.     # _markers -- the marks in the audio file
  345.     #        available through the getmarkers() and getmark()
  346.     #        methods
  347.     # _soundpos -- the position in the audio stream
  348.     #        available through the tell() method, set through the
  349.     #        setpos() method
  350.     #
  351.     # These variables are used internally only:
  352.     # _version -- the AIFF-C version number
  353.     # _decomp -- the decompressor from builtin module cl
  354.     # _comm_chunk_read -- 1 iff the COMM chunk has been read
  355.     # _aifc -- 1 iff reading an AIFF-C file
  356.     # _ssnd_seek_needed -- 1 iff positioned correctly in audio
  357.     #        file for readframes()
  358.     # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk
  359.     # _framesize -- size of one frame in the file
  360.  
  361.     access _file, _nchannels, _nframes, _sampwidth, _framerate, \
  362.           _comptype, _compname, _markers, _soundpos, _version, \
  363.           _decomp, _comm_chunk_read, __aifc, _ssnd_seek_needed, \
  364.           _ssnd_chunk, _framesize: private
  365.  
  366.     def initfp(self, file):
  367.         self._file = file
  368.         self._version = 0
  369.         self._decomp = None
  370.         self._convert = None
  371.         self._markers = []
  372.         self._soundpos = 0
  373.         form = self._file.read(4)
  374.         if form != 'FORM':
  375.             raise Error, 'file does not start with FORM id'
  376.         formlength = _read_long(self._file)
  377.         if formlength <= 0:
  378.             raise Error, 'invalid FORM chunk data size'
  379.         formdata = self._file.read(4)
  380.         formlength = formlength - 4
  381.         if formdata == 'AIFF':
  382.             self._aifc = 0
  383.         elif formdata == 'AIFC':
  384.             self._aifc = 1
  385.         else:
  386.             raise Error, 'not an AIFF or AIFF-C file'
  387.         self._comm_chunk_read = 0
  388.         while formlength > 0:
  389.             self._ssnd_seek_needed = 1
  390.             #DEBUG: SGI's soundfiler has a bug.  There should
  391.             # be no need to check for EOF here.
  392.             try:
  393.                 chunk = Chunk(self._file)
  394.             except EOFError:
  395.                 if formlength == 8:
  396.                     print 'Warning: FORM chunk size too large'
  397.                     formlength = 0
  398.                     break
  399.                 raise EOFError # different error, raise exception
  400.             if chunk.chunkname == 'COMM':
  401.                 self._read_comm_chunk(chunk)
  402.                 self._comm_chunk_read = 1
  403.             elif chunk.chunkname == 'SSND':
  404.                 self._ssnd_chunk = chunk
  405.                 dummy = chunk.read(8)
  406.                 self._ssnd_seek_needed = 0
  407.             elif chunk.chunkname == 'FVER':
  408.                 self._version = _read_long(chunk)
  409.             elif chunk.chunkname == 'MARK':
  410.                 self._readmark(chunk)
  411.             elif chunk.chunkname in _skiplist:
  412.                 pass
  413.             else:
  414.                 raise Error, 'unrecognized chunk type '+chunk.chunkname
  415.             formlength = formlength - 8 - chunk.chunksize
  416.             if chunk.chunksize & 1:
  417.                 formlength = formlength - 1
  418.             if formlength > 0:
  419.                 chunk.skip()
  420.         if not self._comm_chunk_read or not self._ssnd_chunk:
  421.             raise Error, 'COMM chunk and/or SSND chunk missing'
  422.         if self._aifc and self._decomp:
  423.             params = [CL.ORIGINAL_FORMAT, 0, \
  424.                   CL.BITS_PER_COMPONENT, 0, \
  425.                   CL.FRAME_RATE, self._framerate]
  426.             if self._nchannels == AL.MONO:
  427.                 params[1] = CL.MONO
  428.             else:
  429.                 params[1] = CL.STEREO_INTERLEAVED
  430.             if self._sampwidth == AL.SAMPLE_8:
  431.                 params[3] = 8
  432.             elif self._sampwidth == AL.SAMPLE_16:
  433.                 params[3] = 16
  434.             else:
  435.                 params[3] = 24
  436.             self._decomp.SetParams(params)
  437.  
  438.     def __init__(self, f):
  439.         if type(f) == type(''):
  440.             f = builtin.open(f, 'r')
  441.         # else, assume it is an open file object already
  442.         self.initfp(f)
  443.  
  444.     def __del__(self):
  445.         if self._file:
  446.             self.close()
  447.  
  448.     #
  449.     # User visible methods.
  450.     #
  451.     def getfp(self):
  452.         return self._file
  453.  
  454.     def rewind(self):
  455.         self._ssnd_seek_needed = 1
  456.         self._soundpos = 0
  457.  
  458.     def close(self):
  459.         if self._decomp:
  460.             self._decomp.CloseDecompressor()
  461.             self._decomp = None
  462.         self._file = None
  463.  
  464.     def tell(self):
  465.         return self._soundpos
  466.  
  467.     def getnchannels(self):
  468.         return self._nchannels
  469.  
  470.     def getnframes(self):
  471.         return self._nframes
  472.  
  473.     def getsampwidth(self):
  474.         return self._sampwidth
  475.  
  476.     def getframerate(self):
  477.         return self._framerate
  478.  
  479.     def getcomptype(self):
  480.         return self._comptype
  481.  
  482.     def getcompname(self):
  483.         return self._compname
  484.  
  485. ##    def getversion(self):
  486. ##        return self._version
  487.  
  488.     def getparams(self):
  489.         return self.getnchannels(), self.getsampwidth(), \
  490.               self.getframerate(), self.getnframes(), \
  491.               self.getcomptype(), self.getcompname()
  492.  
  493.     def getmarkers(self):
  494.         if len(self._markers) == 0:
  495.             return None
  496.         return self._markers
  497.  
  498.     def getmark(self, id):
  499.         for marker in self._markers:
  500.             if id == marker[0]:
  501.                 return marker
  502.         raise Error, 'marker ' + `id` + ' does not exist'
  503.  
  504.     def setpos(self, pos):
  505.         if pos < 0 or pos > self._nframes:
  506.             raise Error, 'position not in range'
  507.         self._soundpos = pos
  508.         self._ssnd_seek_needed = 1
  509.  
  510.     def readframes(self, nframes):
  511.         if self._ssnd_seek_needed:
  512.             self._ssnd_chunk.rewind()
  513.             dummy = self._ssnd_chunk.read(8)
  514.             pos = self._soundpos * self._framesize
  515.             if pos:
  516.                 self._ssnd_chunk.setpos(pos + 8)
  517.             self._ssnd_seek_needed = 0
  518.         if nframes == 0:
  519.             return ''
  520.         data = self._ssnd_chunk.read(nframes * self._framesize)
  521.         if self._convert and data:
  522.             data = self._convert(data)
  523.         self._soundpos = self._soundpos + len(data) / (self._nchannels * self._sampwidth)
  524.         return data
  525.  
  526.     #
  527.     # Internal methods.
  528.     #
  529.     access *: private
  530.  
  531.     def _decomp_data(self, data):
  532.         dummy = self._decomp.SetParam(CL.FRAME_BUFFER_SIZE,
  533.                           len(data) * 2)
  534.         return self._decomp.Decompress(len(data) / self._nchannels,
  535.                            data)
  536.  
  537.     def _ulaw2lin(self, data):
  538.         import audioop
  539.         return audioop.ulaw2lin(data, 2)
  540.  
  541.     def _read_comm_chunk(self, chunk):
  542.         nchannels = _read_short(chunk)
  543.         self._nchannels = _convert1(nchannels, _nchannelslist)
  544.         self._nframes = _read_long(chunk)
  545.         sampwidth = _read_short(chunk)
  546.         self._sampwidth = _convert1(sampwidth, _sampwidthlist)
  547.         framerate = _read_float(chunk)
  548.         self._framerate = _convert1(framerate, _frameratelist)
  549.         self._framesize = self._nchannels * self._sampwidth
  550.         if self._aifc:
  551.             #DEBUG: SGI's soundeditor produces a bad size :-(
  552.             kludge = 0
  553.             if chunk.chunksize == 18:
  554.                 kludge = 1
  555.                 print 'Warning: bad COMM chunk size'
  556.                 chunk.chunksize = 23
  557.             #DEBUG end
  558.             self._comptype = chunk.read(4)
  559.             #DEBUG start
  560.             if kludge:
  561.                 length = ord(chunk.file.read(1))
  562.                 if length & 1 == 0:
  563.                     length = length + 1
  564.                 chunk.chunksize = chunk.chunksize + length
  565.                 chunk.file.seek(-1, 1)
  566.             #DEBUG end
  567.             self._compname = _read_string(chunk)
  568.             if self._comptype != 'NONE':
  569.                 try:
  570.                     import cl, CL
  571.                 except ImportError:
  572.                     if self._comptype == 'ULAW':
  573.                         try:
  574.                             import audioop
  575.                             self._convert = self._ulaw2lin
  576.                             self._framesize = self._framesize / 2
  577.                             return
  578.                         except ImportError:
  579.                             pass
  580.                     raise Error, 'cannot read compressed AIFF-C files'
  581.                 if self._comptype == 'ULAW':
  582.                     scheme = CL.G711_ULAW
  583.                     self._framesize = self._framesize / 2
  584.                 elif self._comptype == 'ALAW':
  585.                     scheme = CL.G711_ALAW
  586.                     self._framesize = self._framesize / 2
  587.                 else:
  588.                     raise Error, 'unsupported compression type'
  589.                 self._decomp = cl.OpenDecompressor(scheme)
  590.                 self._convert = self._decomp_data
  591.         else:
  592.             self._comptype = 'NONE'
  593.             self._compname = 'not compressed'
  594.  
  595.     def _readmark(self, chunk):
  596.         nmarkers = _read_short(chunk)
  597.         # Some files appear to contain invalid counts.
  598.         # Cope with this by testing for EOF.
  599.         try:
  600.             for i in range(nmarkers):
  601.                 id = _read_short(chunk)
  602.                 pos = _read_long(chunk)
  603.                 name = _read_string(chunk)
  604.                 self._markers.append((id, pos, name))
  605.         except EOFError:
  606.             print 'Warning: MARK chunk contains only',
  607.             print len(self._markers),
  608.             if len(self._markers) == 1: print 'marker',
  609.             else: print 'markers',
  610.             print 'instead of', nmarkers
  611.  
  612. class Aifc_write:
  613.     # Variables used in this class:
  614.     #
  615.     # These variables are user settable through appropriate methods
  616.     # of this class:
  617.     # _file -- the open file with methods write(), close(), tell(), seek()
  618.     #        set through the __init__() method
  619.     # _comptype -- the AIFF-C compression type ('NONE' in AIFF)
  620.     #        set through the setcomptype() or setparams() method
  621.     # _compname -- the human-readable AIFF-C compression type
  622.     #        set through the setcomptype() or setparams() method
  623.     # _nchannels -- the number of audio channels
  624.     #        set through the setnchannels() or setparams() method
  625.     # _sampwidth -- the number of bytes per audio sample
  626.     #        set through the setsampwidth() or setparams() method
  627.     # _framerate -- the sampling frequency
  628.     #        set through the setframerate() or setparams() method
  629.     # _nframes -- the number of audio frames written to the header
  630.     #        set through the setnframes() or setparams() method
  631.     # _aifc -- whether we're writing an AIFF-C file or an AIFF file
  632.     #        set through the aifc() method, reset through the
  633.     #        aiff() method
  634.     #
  635.     # These variables are used internally only:
  636.     # _version -- the AIFF-C version number
  637.     # _comp -- the compressor from builtin module cl
  638.     # _nframeswritten -- the number of audio frames actually written
  639.     # _datalength -- the size of the audio samples written to the header
  640.     # _datawritten -- the size of the audio samples actually written
  641.  
  642.     access _file, _comptype, _compname, _nchannels, _sampwidth, \
  643.           _framerate, _nframes, _aifc, _version, _comp, \
  644.           _nframeswritten, _datalength, _datawritten: private
  645.  
  646.     def __init__(self, f):
  647.         if type(f) == type(''):
  648.             filename = f
  649.             f = builtin.open(f, 'w')
  650.         else:
  651.             # else, assume it is an open file object already
  652.             filename = '???'
  653.         self.initfp(f)
  654.         if filename[-5:] == '.aiff':
  655.             self._aifc = 0
  656.         else:
  657.             self._aifc = 1
  658.  
  659.     def initfp(self, file):
  660.         self._file = file
  661.         self._version = _AIFC_version
  662.         self._comptype = 'NONE'
  663.         self._compname = 'not compressed'
  664.         self._comp = None
  665.         self._convert = None
  666.         self._nchannels = 0
  667.         self._sampwidth = 0
  668.         self._framerate = 0
  669.         self._nframes = 0
  670.         self._nframeswritten = 0
  671.         self._datawritten = 0
  672.         self._datalength = 0
  673.         self._markers = []
  674.         self._marklength = 0
  675.         self._aifc = 1        # AIFF-C is default
  676.  
  677.     def __del__(self):
  678.         if self._file:
  679.             self.close()
  680.  
  681.     #
  682.     # User visible methods.
  683.     #
  684.     def aiff(self):
  685.         if self._nframeswritten:
  686.             raise Error, 'cannot change parameters after starting to write'
  687.         self._aifc = 0
  688.  
  689.     def aifc(self):
  690.         if self._nframeswritten:
  691.             raise Error, 'cannot change parameters after starting to write'
  692.         self._aifc = 1
  693.  
  694.     def setnchannels(self, nchannels):
  695.         if self._nframeswritten:
  696.             raise Error, 'cannot change parameters after starting to write'
  697.         dummy = _convert2(nchannels, _nchannelslist)
  698.         self._nchannels = nchannels
  699.  
  700.     def getnchannels(self):
  701.         if not self._nchannels:
  702.             raise Error, 'number of channels not set'
  703.         return self._nchannels
  704.  
  705.     def setsampwidth(self, sampwidth):
  706.         if self._nframeswritten:
  707.             raise Error, 'cannot change parameters after starting to write'
  708.         dummy = _convert2(sampwidth, _sampwidthlist)
  709.         self._sampwidth = sampwidth
  710.  
  711.     def getsampwidth(self):
  712.         if not self._sampwidth:
  713.             raise Error, 'sample width not set'
  714.         return self._sampwidth
  715.  
  716.     def setframerate(self, framerate):
  717.         if self._nframeswritten:
  718.             raise Error, 'cannot change parameters after starting to write'
  719.         dummy = _convert2(framerate, _frameratelist)
  720.         self._framerate = framerate
  721.  
  722.     def getframerate(self):
  723.         if not self._framerate:
  724.             raise Error, 'frame rate not set'
  725.         return self._framerate
  726.  
  727.     def setnframes(self, nframes):
  728.         if self._nframeswritten:
  729.             raise Error, 'cannot change parameters after starting to write'
  730.         self._nframes = nframes
  731.  
  732.     def getnframes(self):
  733.         return self._nframeswritten
  734.  
  735.     def setcomptype(self, comptype, compname):
  736.         if self._nframeswritten:
  737.             raise Error, 'cannot change parameters after starting to write'
  738.         if comptype not in ('NONE', 'ULAW', 'ALAW'):
  739.             raise Error, 'unsupported compression type'
  740.         self._comptype = comptype
  741.         self._compname = compname
  742.  
  743.     def getcomptype(self):
  744.         return self._comptype
  745.  
  746.     def getcompname(self):
  747.         return self._compname
  748.  
  749. ##    def setversion(self, version):
  750. ##        if self._nframeswritten:
  751. ##            raise Error, 'cannot change parameters after starting to write'
  752. ##        self._version = version
  753.  
  754.     def setparams(self, (nchannels, sampwidth, framerate, nframes, comptype, compname)):
  755.         if self._nframeswritten:
  756.             raise Error, 'cannot change parameters after starting to write'
  757.         if comptype not in ('NONE', 'ULAW', 'ALAW'):
  758.             raise Error, 'unsupported compression type'
  759.         dummy = _convert2(nchannels, _nchannelslist)
  760.         dummy = _convert2(sampwidth, _sampwidthlist)
  761.         dummy = _convert2(framerate, _frameratelist)
  762.         self._nchannels = nchannels
  763.         self._sampwidth = sampwidth
  764.         self._framerate = framerate
  765.         self._nframes = nframes
  766.         self._comptype = comptype
  767.         self._compname = compname
  768.  
  769.     def getparams(self):
  770.         if not self._nchannels or not self._sampwidth or not self._framerate:
  771.             raise Error, 'not all parameters set'
  772.         return self._nchannels, self._sampwidth, self._framerate, \
  773.               self._nframes, self._comptype, self._compname
  774.  
  775.     def setmark(self, id, pos, name):
  776.         if id <= 0:
  777.             raise Error, 'marker ID must be > 0'
  778.         if pos < 0:
  779.             raise Error, 'marker position must be >= 0'
  780.         if type(name) != type(''):
  781.             raise Error, 'marker name must be a string'
  782.         for i in range(len(self._markers)):
  783.             if id == self._markers[i][0]:
  784.                 self._markers[i] = id, pos, name
  785.                 return
  786.         self._markers.append((id, pos, name))
  787.  
  788.     def getmark(self, id):
  789.         for marker in self._markers:
  790.             if id == marker[0]:
  791.                 return marker
  792.         raise Error, 'marker ' + `id` + ' does not exist'
  793.  
  794.     def getmarkers(self):
  795.         if len(self._markers) == 0:
  796.             return None
  797.         return self._markers
  798.                 
  799.     def tell(self):
  800.         return self._nframeswritten
  801.  
  802.     def writeframesraw(self, data):
  803.         self._ensure_header_written(len(data))
  804.         nframes = len(data) / (self._sampwidth * self._nchannels)
  805.         if self._convert:
  806.             data = self._convert(data)
  807.         self._file.write(data)
  808.         self._nframeswritten = self._nframeswritten + nframes
  809.         self._datawritten = self._datawritten + len(data)
  810.  
  811.     def writeframes(self, data):
  812.         self.writeframesraw(data)
  813.         if self._nframeswritten != self._nframes or \
  814.               self._datalength != self._datawritten:
  815.             self._patchheader()
  816.  
  817.     def close(self):
  818.         self._ensure_header_written(0)
  819.         if self._datawritten & 1:
  820.             # quick pad to even size
  821.             self._file.write(chr(0))
  822.             self._datawritten = self._datawritten + 1
  823.         self._writemarkers()
  824.         if self._nframeswritten != self._nframes or \
  825.               self._datalength != self._datawritten or \
  826.               self._marklength:
  827.             self._patchheader()
  828.         if self._comp:
  829.             self._comp.CloseCompressor()
  830.             self._comp = None
  831.         self._file.flush()
  832.         self._file = None
  833.  
  834.     #
  835.     # Internal methods.
  836.     #
  837.     access *: private
  838.  
  839.     def _comp_data(self, data):
  840.         dum = self._comp.SetParam(CL.FRAME_BUFFER_SIZE, len(data))
  841.         dum = self._comp.SetParam(CL.COMPRESSED_BUFFER_SIZE, len(data))
  842.         return self._comp.Compress(nframes, data)
  843.  
  844.     def _lin2ulaw(self, data):
  845.         import audioop
  846.         return audioop.lin2ulaw(data, 2)
  847.  
  848.     def _ensure_header_written(self, datasize):
  849.         if not self._nframeswritten:
  850.             if self._comptype in ('ULAW', 'ALAW'):
  851.                 if not self._sampwidth:
  852.                     self._sampwidth = AL.SAMPLE_16
  853.                 if self._sampwidth != AL.SAMPLE_16:
  854.                     raise Error, 'sample width must be 2 when compressing with ULAW or ALAW'
  855.             if not self._nchannels:
  856.                 raise Error, '# channels not specified'
  857.             if not self._sampwidth:
  858.                 raise Error, 'sample width not specified'
  859.             if not self._framerate:
  860.                 raise Error, 'sampling rate not specified'
  861.             self._write_header(datasize)
  862.  
  863.     def _init_compression(self):
  864.         try:
  865.             import cl, CL
  866.         except ImportError:
  867.             if self._comptype == 'ULAW':
  868.                 try:
  869.                     import audioop
  870.                     self._convert = self._lin2ulaw
  871.                     return
  872.                 except ImportError:
  873.                     pass
  874.             raise Error, 'cannot write compressed AIFF-C files'
  875.         if self._comptype == 'ULAW':
  876.             scheme = CL.G711_ULAW
  877.         elif self._comptype == 'ALAW':
  878.             scheme = CL.G711_ALAW
  879.         else:
  880.             raise Error, 'unsupported compression type'
  881.         self._comp = cl.OpenCompressor(scheme)
  882.         params = [CL.ORIGINAL_FORMAT, 0, \
  883.               CL.BITS_PER_COMPONENT, 0, \
  884.               CL.FRAME_RATE, self._framerate, \
  885.               CL.FRAME_BUFFER_SIZE, 100, \
  886.               CL.COMPRESSED_BUFFER_SIZE, 100]
  887.         if self._nchannels == AL.MONO:
  888.             params[1] = CL.MONO
  889.         else:
  890.             params[1] = CL.STEREO_INTERLEAVED
  891.         if self._sampwidth == AL.SAMPLE_8:
  892.             params[3] = 8
  893.         elif self._sampwidth == AL.SAMPLE_16:
  894.             params[3] = 16
  895.         else:
  896.             params[3] = 24
  897.         self._comp.SetParams(params)
  898.         # the compressor produces a header which we ignore
  899.         dummy = self._comp.Compress(0, '')
  900.         self._convert = self._comp_data
  901.  
  902.     def _write_header(self, initlength):
  903.         if self._aifc and self._comptype != 'NONE':
  904.             self._init_compression()
  905.         self._file.write('FORM')
  906.         if not self._nframes:
  907.             self._nframes = initlength / (self._nchannels * self._sampwidth)
  908.         self._datalength = self._nframes * self._nchannels * self._sampwidth
  909.         if self._datalength & 1:
  910.             self._datalength = self._datalength + 1
  911.         if self._aifc and self._comptype in ('ULAW', 'ALAW'):
  912.             self._datalength = self._datalength / 2
  913.             if self._datalength & 1:
  914.                 self._datalength = self._datalength + 1
  915.         self._form_length_pos = self._file.tell()
  916.         commlength = self._write_form_length(self._datalength)
  917.         if self._aifc:
  918.             self._file.write('AIFC')
  919.             self._file.write('FVER')
  920.             _write_long(self._file, 4)
  921.             _write_long(self._file, self._version)
  922.         else:
  923.             self._file.write('AIFF')
  924.         self._file.write('COMM')
  925.         _write_long(self._file, commlength)
  926.         _write_short(self._file, _convert2(self._nchannels, _nchannelslist))
  927.         self._nframes_pos = self._file.tell()
  928.         _write_long(self._file, self._nframes)
  929.         _write_short(self._file, _convert2(self._sampwidth, _sampwidthlist))
  930.         _write_float(self._file, _convert2(self._framerate, _frameratelist))
  931.         if self._aifc:
  932.             self._file.write(self._comptype)
  933.             _write_string(self._file, self._compname)
  934.         self._file.write('SSND')
  935.         self._ssnd_length_pos = self._file.tell()
  936.         _write_long(self._file, self._datalength + 8)
  937.         _write_long(self._file, 0)
  938.         _write_long(self._file, 0)
  939.  
  940.     def _write_form_length(self, datalength):
  941.         if self._aifc:
  942.             commlength = 18 + 5 + len(self._compname)
  943.             if commlength & 1:
  944.                 commlength = commlength + 1
  945.             verslength = 12
  946.         else:
  947.             commlength = 18
  948.             verslength = 0
  949.         _write_long(self._file, 4 + verslength + self._marklength + \
  950.                     8 + commlength + 16 + datalength)
  951.         return commlength
  952.  
  953.     def _patchheader(self):
  954.         curpos = self._file.tell()
  955.         if self._datawritten & 1:
  956.             datalength = self._datawritten + 1
  957.             self._file.write(chr(0))
  958.         else:
  959.             datalength = self._datawritten
  960.         if datalength == self._datalength and \
  961.               self._nframes == self._nframeswritten and \
  962.               self._marklength == 0:
  963.             self._file.seek(curpos, 0)
  964.             return
  965.         self._file.seek(self._form_length_pos, 0)
  966.         dummy = self._write_form_length(datalength)
  967.         self._file.seek(self._nframes_pos, 0)
  968.         _write_long(self._file, self._nframeswritten)
  969.         self._file.seek(self._ssnd_length_pos, 0)
  970.         _write_long(self._file, datalength + 8)
  971.         self._file.seek(curpos, 0)
  972.         self._nframes = self._nframeswritten
  973.         self._datalength = datalength
  974.  
  975.     def _writemarkers(self):
  976.         if len(self._markers) == 0:
  977.             return
  978.         self._file.write('MARK')
  979.         length = 2
  980.         for marker in self._markers:
  981.             id, pos, name = marker
  982.             length = length + len(name) + 1 + 6
  983.             if len(name) & 1 == 0:
  984.                 length = length + 1
  985.         _write_long(self._file, length)
  986.         self._marklength = length + 8
  987.         _write_short(self._file, len(self._markers))
  988.         for marker in self._markers:
  989.             id, pos, name = marker
  990.             _write_short(self._file, id)
  991.             _write_long(self._file, pos)
  992.             _write_string(self._file, name)
  993.  
  994. def open(f, mode):
  995.     if mode == 'r':
  996.         return Aifc_read(f)
  997.     elif mode == 'w':
  998.         return Aifc_write(f)
  999.     else:
  1000.         raise Error, "mode must be 'r' or 'w'"
  1001.  
  1002. openfp = open # B/W compatibility
  1003.